package org.semanticflash.rdf.graph
{
	
	
	import com.bigflexlib.ds.DataStructureEvent;
	import com.bigflexlib.ds.container.ContainerDiff;
	import com.bigflexlib.ds.container.bag.IOWBag;
	import com.bigflexlib.ds.container.bag.impl.owBag;
	import com.bigflexlib.ds.container.zet.IOSet;
	import com.bigflexlib.ds.container.zet.IOWSet;
	import com.bigflexlib.ds.container.zet.IRSet;
	import com.bigflexlib.ds.container.zet.impl.owSet;
	import com.bigflexlib.ds.op.ContainerToSetOp;
	import com.bigflexlib.ds.op.ContainerUnionOp;
	import com.bigflexlib.ds.op.SetMappingOp;
	import com.bigflexlib.utils.IOperator;
	
	import flash.utils.Dictionary;
	
	import org.semanticflash.rdf.graph.redirection.RDFRedirectionContext;
	import org.semanticflash.rdf.triple.IRDFTripleProvider;
	import org.semanticflash.rdf.triple.RDFTriple;
	

	
	/**
	 * 
	 * 
	 * A graph is a Set of sources of rdf triples.
	 * it provides the following functionality
	 * - indexation
	 * - views
	 * - redirections ( smushing )
	 * 
	 * Reasoning, scuttering, etc are NOT provided directly by the graph.
	 * 
	 * @author aldo.bucchi@gmail.com
	 * 
	 */	
	public class RDFGraph implements IRDFTripleProvider
	{
		
		
		

		//////////////////////////////////////////////
		// redirectionContext
		//////////////////////////////////////////////		
		
		private var _redirectionContext:RDFRedirectionContext;
		
		
		public function get redirectionContext():RDFRedirectionContext
		{
			return _redirectionContext;
		}
		
		
		
		
		
		//////////////////////////////////////////////
		// sources
		//////////////////////////////////////////////			
		
		
		private var _sources:IOWSet;
		/**
		 * 
		 * If you want to add data to the graph, create a source and put it here.
		 * Sources must implement the IRDFTripleProvider interface.
		 * <p>
		 * This property is exposed as an observe-write set instead of custom methods
		 * to allow direct set operations.
		 * </p>
		 * <p>
		 * The tradeoff is that there is no type checking. So watch out.
		 * </p> 
		 * 
		 * @return 
		 * 
		 */		
		public function get sources( ):IOWSet
		{
			return _sources;		
		}
		
		
		
		//////////////////////////////////////////////
		// internalSources
		//////////////////////////////////////////////			
		

		/**
		 * 
		 * Will hold other kinds of sources: statements ( redirected ) and inferences
		 * 
		*/		
		internal var implicitSources:IOWSet;
		
		
		
		
		
		//////////////////////////////////////////////
		// allSources
		//////////////////////////////////////////////			
		
		
		/**
		 * 
		 * The union of "sources" and "implicitSources"
		 * 
		*/		
		private var allSources:IOWSet;
		
		
		// reference to the union operator in use
		private var unionOp:IOperator;
		
		
		
		
		
		
		
		
		
		//////////////////////////////////////////////
		// triples
		//////////////////////////////////////////////			
		
		/**
		 * @private
		 * 
		 */
		internal var _triples:IOWSet;

		
		/**
		 *
		 * A set containing the asserted triples in this graph
		 *  
		 * @return 
		 * 
		 */		
		public function get triples( ):IOSet
		{
			return _triples;		
		}
		
		
		//////////////////////////////////////////////
		// etc...
		//////////////////////////////////////////////			
		
		
		/**
		 * 
		 * Not all statements are published. Only those that are true and not redirected.
		 * 
		*/		
		internal var publishedStatements:IOWSet;
		
		
		
		/**
		 * 
		 * Some operators we create internally
		 * 
		*/		
		private var ops:Array = [ ];
		
		
		
		
	
	
		//////////////////////////////////////////////
		// constructor
		//////////////////////////////////////////////			
		

		public function RDFGraph( iriCompareFunction:Function = null  )
		{

			_triples = owSet();

			publishedStatements = owSet();
			

			// connect both... each published statement contributes one triple
			ops.push( new SetMappingOp( publishedStatements, _triples, mapStatementToTriple ) );


			_redirectionContext = new RDFRedirectionContext( iriCompareFunction );
			

			initSources( );

		}
		


		private function initSources( ):void
		{

			// ( explicit ) sources
			_sources = owSet( );
			
			// implicit sources
			implicitSources = owSet( );
			
			// all sources
			allSources = owSet( );
			
			// intermediate structure used to link operators
			var bagLink:IOWBag = owBag( );


			// connect them
			ops.push(  new ContainerUnionOp( owSet( [ _sources, implicitSources ] ), bagLink ) );
			ops.push( new ContainerToSetOp( bagLink, allSources ) );
			
			
			// event listener to the allSources
			allSources.addEventListener( DataStructureEvent.CHANGE, allSources_change );		
		
		}








		private function mapStatementToTriple( e:* ):*
		{
			return ( e as RDFStatement ).triple;
		}












		//////////////////////////////////////////////
		// statements
		//////////////////////////////////////////////
		
		
		private var statements:Dictionary = new Dictionary( );


		public function getStatement( triple:RDFTriple ):RDFStatement
		{

			if ( ! statements[ triple ] )
			{

				var st:RDFStatement = new RDFStatement( triple ,this );

				statements[ triple ] = st;

				return st;							

			}			

			return statements[ triple ] as RDFStatement;		

		}




		//////////////////////////////////////////////
		// sources 
		//////////////////////////////////////////////



		/**
		 * 
		 * This is needed to keep a backreference from source.triples to source.
		 * It is used in an event listener below
		 * 
		 */
		private var tripleSet2source:Dictionary = new Dictionary( true );



		/**
		 * 
		 * Called when the sources set changes ( a source has been removed or a new source has been added )
		 * 
		 * @param event
		 * 
		 */
		private function allSources_change( event:DataStructureEvent ):void
		{
		
			_triples.autoUpdateFlag.disable( );
			
		
			( event.diff as ContainerDiff ).walk( walkSourcesDiff );			
		
		
			_triples.autoUpdateFlag.enable( );
		
		}


		
			private function walkSourcesDiff( e:*, m:int ):void
			{
			
				var source:IRDFTripleProvider = e as IRDFTripleProvider;
			
				// we know m is either 1 or -1 ( because this is a set diff )
				
				if ( m > 0 ) // add
				{
					// add event listener to detect changes to the triple set
					source.triples.addEventListener( DataStructureEvent.CHANGE, source_triples_change );
					
					// state current triples in the graph
					state( source.triples, source, true );
										
					// add a reference so we can know which source owns this triple set
					tripleSet2source[ source.triples ] = source;
										
				}
				else // remove ( inverse operations )
				{
					source.triples.removeEventListener( DataStructureEvent.CHANGE, source_triples_change );
					state( source.triples, source, false );
					delete tripleSet2source[ source.triples ];
				}
			
			}
		


		/**
		 * 
		 * Called when each source's triples change
		 * 
		 * @param event
		 * 
		 */
		private function source_triples_change( event:DataStructureEvent ):void
		{
			
			_triples.autoUpdateFlag.disable( );
			
			
			var diff:ContainerDiff = event.diff as ContainerDiff;
			
			var source:IRDFTripleProvider = tripleSet2source[ event.target ] as IRDFTripleProvider;
						
			state( diff.removedElements, source, false );
			state( diff.addedElements, source, true );
			
		
		
			_triples.autoUpdateFlag.enable( );
					
		}


		
		
		/**
		 * 
		 * Used internally to ease the statement of triples
		 * 
		 * @param triples
		 * @param source
		 * @param state
		 * 
		 */		
		private function state( triples:IRSet, source:IRDFTripleProvider, state:Boolean ):void
		{
			triples.walk( 
				function( e:*, m:int ):void
				{
					// we know that m is always 1 ( this is a set )
					var statement:RDFStatement = getStatement( e as RDFTriple );
					if ( state )
						statement.addSource( source );
					else
						statement.removeSource( source );
				}
			 );		
		}


	}	

}